﻿global using WXWorkSvc.Common.Data;

using Azure.Core;

using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using System.Diagnostics;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text;
using System.Web;

using WXWorkSvc.Common.Extensions;
using WXWorkSvc.Common.Models;

namespace WXWorkSvc.Models;

public class WXWork3rd
{
    private readonly HttpClient _HC;
    private readonly IHttpContextAccessor _Accessor;
    private readonly ILogger<WXWork3rd> _Logger;
    private readonly IServiceProvider _ServiceProvider;
    public WXWork3rd(HttpClient hc, IHttpContextAccessor acc, ILogger<WXWork3rd> logger, IServiceProvider provider)
    {
        _HC = hc;
        _HC.BaseAddress = new Uri("https://qyapi.weixin.qq.com");
        _Accessor = acc;
        _Logger = logger;
        _ServiceProvider = provider;
    }

    public async Task<string> GetAgentAuthUrlAsync(string suiteId, string rediretUri, bool test)
    {
        var suiteAccessToken = await GetSuiteAccessTokenAsync(suiteId);
        var resPreAuth = await _HC.GetFromJsonAsync<ResponsePreAuth>($"cgi-bin/service/get_pre_auth_code?suite_access_token={suiteAccessToken}");
        resPreAuth.Valid();
        var resMsg = await _HC.PostAsJsonAsync($"cgi-bin/service/set_session_info?suite_access_token={suiteAccessToken}", new
        {
            pre_auth_code = resPreAuth!.pre_auth_code,
            session_info = new { auth_type = test ? 1 : 0 }
        });
        _Logger.LogInformation(await resMsg.Content.ReadAsStringAsync());

        return $"https://open.work.weixin.qq.com/3rdapp/install?suite_id={suiteId}&pre_auth_code={resPreAuth!.pre_auth_code}&redirect_uri={HttpUtility.UrlEncode(rediretUri)}&state=STATE";
    }

    public async Task SendTextMessageAsync(string suiteId, string corpId, string? userIds, string message)
    {
        if (string.IsNullOrEmpty(userIds))
            return;
        if (userIds.Split('|').Where(ii => !string.IsNullOrEmpty(ii)).Count() == 0)
            return;

        var item = await GetAccessTokenItemAsync(suiteId, corpId);
        await _HC.PostAsJsonAsync($"cgi-bin/message/send?access_token={item.Token}", new
        {
            touser = userIds,
            agentid = item.AgentId,
            msgtype = "text",
            text = new
            {
                content = message
            }
        });
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="userIds">指定接收消息的成员，成员ID列表（多个接收者用‘|’分隔，最多支持1000个）。    特殊情况：指定为"@all"，则向该企业应用的全部成员发送</param>
    /// <param name="message">markdown内容，最长不超过2048个字节，必须是utf8编码</param>
    /// <returns></returns>
    public async Task SendMDMessageAsync(string suiteId, string corpId, string? userIds, string message)
    {

        if (string.IsNullOrEmpty(userIds))
            return;
        if (userIds.Split('|').Where(ii => !string.IsNullOrEmpty(ii)).Count() == 0)
            return;


        var item = await GetAccessTokenItemAsync(suiteId, corpId);
        await _HC.PostAsJsonAsync($"cgi-bin/message/send?access_token={item.Token}", new
        {
            touser = userIds,
            agentid = item.AgentId,
            msgtype = "markdown",
            markdown = new
            {
                content = message
            }
        });
    }

    private async Task<string> GetAccessTokenAsync(string suiteId, string corpId)
    {
        var item = await GetAccessTokenItemAsync(suiteId, corpId);
        return item.Token;
    }
    private async Task<TAgent> GetAccessTokenItemAsync(string suiteId, string corpId)
    {
        using var db = getdb();
        var item = await db.Agents.FindAsync(suiteId, corpId);

        if (item!.DTExpiresToken < DateTime.Now)
        {
            //using var db = GetDb();
            //var item = await db.AuthCorps.Where(ii => ii.Id == _CorpId).Select(ii => new { ii.AuthCorpId, ii.PermanentCode, ii.AgentId }).FirstAsync();
            var rmsg = await _HC.PostAsJsonAsync($"cgi-bin/service/get_corp_token?suite_access_token={await GetSuiteAccessTokenAsync(suiteId)}", new
            {
                auth_corpid = corpId,
                permanent_code = item.PermanentCode
            });
            rmsg.EnsureSuccessStatusCode();
            var ret = await rmsg.Content.ReadFromJsonAsync<ResponseAccessToken>();
            ret.Valid();
            item.Token = ret!.access_token;
            item.DTExpiresToken = DateTime.Now.AddSeconds(ret.expires_in);
        }
        return item;
    }
    private async Task<TAgent> GetJSTicketAsync(string suiteId, string corpId)
    {
        var item = await GetAccessTokenItemAsync(suiteId, corpId);
        if (item.DTExpiresTicket < DateTime.Now)
        {
            var ret = await _HC.GetFromJsonAsync<ResponseJsTicket>($"cgi-bin/get_jsapi_ticket?access_token={item.Token}");
            ret.Valid();
            var retAgent = await _HC.GetFromJsonAsync<ResponseJsTicket>($"cgi-bin/ticket/get?access_token={item.Token}&type=agent_config");
            retAgent.Valid();
            item.DTExpiresTicket = DateTime.Now.AddSeconds(ret!.expires_in);
            item.JsTicket = ret.ticket;
            item.AgentJsTicket = retAgent!.ticket;
            using var db = getdb();
            db.Attach(item).State = EntityState.Modified;
            await db.SaveChangesAsync();
        }
        return item;
    }

    private async Task<string> GetSuiteAccessTokenAsync(string suiteId)
    {
        var item = await GetSuiteAccessTokenItemAsync(suiteId);
        return item.Token;
    }

    private async Task<TSuite> GetSuiteAccessTokenItemAsync(string suiteId)
    {
        using var db = getdb();
        var item = await db.Suites.FindAsync(suiteId);

        if (item!.DTTokenExpires < DateTime.Now)
        {
            var rmsg = await _HC.PostAsJsonAsync("cgi-bin/service/get_suite_token", new
            {
                suite_id = item.Id,
                suite_secret = item.Secret,
                suite_ticket = item.Ticket
            });

            rmsg.EnsureSuccessStatusCode();
            var ret = await rmsg.Content.ReadFromJsonAsync<ResponseSuiteAccessToken>();
            ret.Valid();

            item.Token = ret!.suite_access_token;
            item.DTTokenExpires = DateTime.Now.AddSeconds(ret.expires_in);
            await db.SaveChangesAsync();
        }
        return item;
    }
    public async Task<string> GetAgentJsConfigAsync(HttpRequest request, params string[] jsApiList)
    {
        return await GetAgentJsConfigAsync(request, request.Query["suiteId"].ToString(), request.Query["corpId"].ToString(), jsApiList);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="url"></param>
    /// <param name="jsApiList"></param>
    /// <returns></returns>
    public async Task<string> GetAgentJsConfigAsync(HttpRequest request, string suiteId, string corpId, params string[] jsApiList)
    {
        var url = request.GetDisplayUrl();
        if (url.ToLower().Contains("localhost"))
            return "";
        var index = url.IndexOf("#");
        if (index > 0)
            url = url.Substring(0, index);

        var item = await GetJSTicketAsync(suiteId, corpId);
        var noncestr = DateTime.Now.Ticks.ToString();
        var timestamp = noncestr;

        var str = $"jsapi_ticket={item.AgentJsTicket}&noncestr={noncestr}&timestamp={timestamp}&url={url}";

        var agentSign = Encoding.UTF8.GetBytes(str).Sha1Sign();

        if (request.IsWXBrowse())
        {
            str = $"jsapi_ticket={item.JsTicket}&noncestr={noncestr}&timestamp={timestamp}&url={url}";
            var sign = Encoding.UTF8.GetBytes(str).Sha1Sign();
            return @$"
<script src=""https://res.wx.qq.com/open/js/jweixin-1.2.0.js"" referrerpolicy=""origin""></script>
<script src=""https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"" referrerpolicy=""origin""></script>
<script>
var WXReady=false;
wx.config({{beta: true,
    debug: false,
    appId: '{item.CorpId}',
    timestamp: '{timestamp}',
    nonceStr: '{noncestr}', 
    signature: '{sign}',
    jsApiList: ['{string.Join("','", jsApiList)}']
}});

wx.ready(function(){{
    wx.agentConfig({{
        corpid: '{item.CorpId}',
        agentid: '{item.AgentId}', // 必填，企业微信的应用id （e.g. 1000247）
        timestamp:'{timestamp}' , // 必填，生成签名的时间戳
        nonceStr: '{noncestr}', // 必填，生成签名的随机串
        signature: '{agentSign}',// 必填，签名，见附录-JS-SDK使用权限签名算法
        jsApiList: [''], //必填，传入需要使用的接口名称
	    success: function(res) {{
       	    WWOpenData.bind(document.querySelector('ww-open-data'));
        }},
        fail: function(res) {{
//var str=JSON.stringify(res);
//alert(""fail""+str);
            if(res.errMsg.indexOf('function not exist') > -1){{
                alert('版本过低请升级')
            }}
        }}
    }});


}});
</script>
";
        }
        else
            return $@"
<script src=""https://res.wx.qq.com/open/js/jweixin-1.2.0.js"" referrerpolicy=""origin""></script>
<script src=""https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"" referrerpolicy=""origin""></script>
<script>

wx.agentConfig({{
    corpid: '{item.CorpId}',
    agentid: '{item.AgentId}', // 必填，企业微信的应用id （e.g. 1000247）
    timestamp:'{timestamp}' , // 必填，生成签名的时间戳
    nonceStr: '{noncestr}', // 必填，生成签名的随机串
    signature: '{agentSign}',// 必填，签名，见附录-JS-SDK使用权限签名算法
    jsApiList: [''], //必填，传入需要使用的接口名称
	success: function(res) {{
       	WWOpenData.bind(document.querySelector('ww-open-data'));
    }},
    fail: function(res) {{
//var str=JSON.stringify(res);
//alert(""fail""+str);
        if(res.errMsg.indexOf('function not exist') > -1){{
            alert('版本过低请升级')
        }}
    }}
}});

</script>

";


    }





    #region Auth
    public async Task<string> CreateAuthAsync(string suiteId, string auth_code)
    {
        var suiteItem = await GetSuiteAccessTokenItemAsync(suiteId);
        var msg = await _HC.PostAsJsonAsync($"cgi-bin/service/get_permanent_code?suite_access_token={suiteItem.Token}", new { auth_code });
        msg.EnsureSuccessStatusCode();
        var v = await msg.Content.ReadFromJsonAsync<ResponseCreateAuth>();
        v.Valid();

        using var db = getdb();
        var corpItem = await db.Corps.FindAsync(v!.auth_corp_info.corpid);
        if (corpItem is null)
        {
            corpItem = new TCorp { Id = v.auth_corp_info.corpid, Dt = DateTime.Now };
            db.Corps.Add(corpItem);
        }
        corpItem.Name = v.auth_corp_info.corp_full_name;
        corpItem.IsAuth = true;

        var item = await db.Agents.FindAsync(suiteId, v.auth_corp_info.corpid);
        if (item is null)
        {
            item = new TAgent { SuiteId = suiteId, CorpId = v.auth_corp_info.corpid };
            db.Agents.Add(item);
        }

        item.AgentId = v.auth_info.agent[0].agentid;
        item.PermanentCode = v.permanent_code;
        item.Token = v.access_token;
        item.DTExpiresToken = DateTime.Now.AddSeconds(v.expires_in);

        await db.SaveChangesAsync();

        await SetAllowedUserAsync(suiteItem.Token, item, v.auth_info.agent[0].privilege);
        return suiteItem.Url!;
    }
    public async Task<ResponseCorpAuthInfo> GetCorpAuthInfoAsync(string suiteId, string auth_corpid)
    {
        var suiteAccessToken = await GetSuiteAccessTokenAsync(suiteId);
        var agentItem = await GetAccessTokenItemAsync(suiteId, auth_corpid);
        var msg = await _HC.PostAsJsonAsync($"cgi-bin/service/get_auth_info?suite_access_token={suiteAccessToken}", new { auth_corpid, permanent_code = agentItem.PermanentCode });
        msg.EnsureSuccessStatusCode();
        var v = await msg.Content.ReadFromJsonAsync<ResponseCorpAuthInfo>();
        v.Valid();

        await SetAllowedUserAsync(suiteAccessToken, agentItem, v!.auth_info.agent[0].privilege);

        return v!;
    }

    private async Task SetAllowedUserAsync(string suiteAccessToken, TAgent agent, AuthInfo_Privilege p)
    {
        Stopwatch sw = Stopwatch.StartNew();
        List<string> userIds = new List<string>();
        if (p.allow_user.Length > 0)
            userIds.AddRange(p.allow_user);
        if (p.allow_tag.Length > 0)
        {
            foreach (var tagId in p.allow_tag)
            {
                var tagDetail = await _HC.GetFromJsonAsync<ResponseTagDetail>($"cgi-bin/tag/get?access_token={agent.Token}&tagid={tagId}");
                if (tagDetail?.userlist?.Length > 0)
                    userIds.AddRange(tagDetail.userlist.Select(ii => ii.userid));
            }
        }

        var departlist = await _HC.GetFromJsonAsync<ResponseDeparts>($"cgi-bin/department/simplelist?access_token={agent.Token}");
        if (departlist?.department_id?.Length > 0)
        {

            foreach (var did in departlist.department_id)
            {
                var retuserlist = await _HC.GetFromJsonAsync<ResponseDepartUsers>($"cgi-bin/user/simplelist?access_token={agent.Token}&department_id={did.id}");
                if (retuserlist?.userlist?.Length > 0)
                    userIds.AddRange(retuserlist.userlist.Select(ii => ii.userid));
            }
        }
        userIds = userIds.Distinct().ToList();

        var adminMsg = await _HC.PostAsJsonAsync($"cgi-bin/service/get_admin_list?suite_access_token={suiteAccessToken}", new { auth_corpid = agent.CorpId, agentid = agent.AgentId });
        adminMsg.EnsureSuccessStatusCode();
        var admins = await adminMsg.Content.ReadFromJsonAsync<ResponseAdmins>();
        var adminUserIds = admins!.admin.Select(ii => ii.userid).ToList();
        admins.Valid();

        using var db = getdb();
        var sql = @$"
            with tt as(select * from users where suiteid={{0}} and corpid={{1}})
            merge tt
            using (
            		values {string.Join(',', userIds.Select(ii => $"('{ii}')"))}
            		)as src (userid)
            on tt.userid=src.userid
            when not matched then 
            	INSERT ([SuiteId],[CorpId],[UserId],[Roles]) VALUES ({{0}},{{1}},src.userid,0)
            when not matched by source then delete;

            update users set roles=roles&~1 where suiteid={{0}} and corpid={{1}};

            with tt as(select * from users where suiteid={{0}} and corpid={{1}})
            merge tt
            using (
            		values {string.Join(',', adminUserIds.Select(ii => $"('{ii}')"))}
            		)as src (userid)
            on tt.userid=src.userid
            when matched then update set roles=(roles|1);
                    update users set roles=roles&~1 where suiteid={{0}} and corpid={{1}};
            with tt as(select * from users where suiteid={{0}} and corpid={{1}})
            merge tt
            using (
            		values {string.Join(',', adminUserIds.Select(ii => $"('{ii}')"))}
            		)as src (userid)
            on tt.userid=src.userid
            when matched then update set roles=(roles|1);
            ";
        await db.Database.ExecuteSqlRawAsync(sql, agent.SuiteId, agent.CorpId);



        sw.Stop();
        _Logger.LogInformation("Finish " + nameof(SetAllowedUserAsync) + sw.ElapsedMilliseconds);

    }


    #endregion

    private WXDbContext getdb() => _ServiceProvider.CreateScope().ServiceProvider.GetRequiredService<WXDbContext>();
}
